//=============================================================================
// Project  : NIPO Component Framework
// File    : NIPOController.cs
// Author  : Bernhard Dieber (Bernhard.Dieber@uni-klu.ac.at)
// Copyright 2010 by Bernhard Dieber
// This code is published under the Microsoft Public License (Ms-PL).  A copy
// of the license should be distributed with the code.  It can also be found
// at the project website: http://NIPO.CodePlex.com.   This notice, the
// author's name, and all copyright notices must remain intact in all
// applications, documentation, and source files.
//=============================================================================


using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using NIPO.Plugins;
using NIPO.Util;
using NIPO.Data;
using System.Runtime.Serialization.Formatters.Soap;

namespace NIPO.Controller
{
    /// <summary>
    /// The controller is the facade presented to an outside application.
    /// Using the NIPO controller an application can construct new NIPO instances and perform all necessary operation to control it.<br />
    /// The NIPO-controller holds all selected plugins (1...* Input, 0 or 1 processing, 1...* output, * services, * observers) where the Input, Processing and Output plugins form the processing chain.<br />
    /// Plugins in the processing chain are checked for their intercompatibility before the NIPO-controller starts a given instance. This is the last security check before an instance is allowed to run.<br />
    /// 
    /// The NIPO-controller implements a singleton pattern and is thus accessible via static accessors.<br />
    /// If an application needs multiple NIPO-instances it is suggested to create a separate AppDomain for each instance (this is done to prevent from unexpected data inconsitency)
    /// </summary>
    /// <example>
    /// This example shows how to create a new NIPO instance by loading plugins and adding them to the selected plugins. The application gets data via the input plugin, processes the data, writes the result to a file and additionally displays a visualization.
    /// <code>
    /// MyInputPlugin input=new MyInputPlugin();
    /// MyProcessingPlugin processing=new MyProcessingPlugin();
    /// MyFileOutputPlugin fileout=new MyFileOutputPlugin();
    /// MyVisualizationPlugin visualization=new MyVisualizationPlugin();
    /// /*
    /// ...configure plugins
    /// */
    /// NIPOController.Instance.AddInputPlugin(input);
    /// NIPOController.Instance.AddProcessingPlugin(processing);
    /// NIPOController.Instance.AddOutputPlugin(fileout);
    /// NIPOController.Instance.AddOutputPlugin(visualization);
    /// NIPOController.Instance.Start();
    /// </code>
    /// This example shows how the NIPOController can be used to search for suitable plugins.
    /// <code>
    /// <![CDATA[
    ///        NIPOController controller = NIPOController.Instance;
    ///         /* A processing chain can be used to chain up multiple processing plugins */
    ///        NIPO.Plugins.Extension.ProcessingChainPlugin chain = new NIPO.Plugins.Extension.ProcessingChainPlugin();
    ///         /* Search for all plugins in the subfolder "Plugins" */
    ///        List<FileInfo> assemblies = NIPOController.SearchAssemblies(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "Plugins")),true);
    ///        foreach (FileInfo info in assemblies)
    ///        {
    ///            List<Type> inputTypes = NIPOController.SearchPlugIns(info.FullName, NIPO.Plugins.PlugInType.Input);
    ///            foreach (Type type in inputTypes)
    ///            {
    ///                Console.WriteLine(type.FullName);
    ///                InputPlugin input=(InputPlugin)type.Assembly.CreateInstance(type.FullName);
    ///             /*
    ///             ... Configure Plugins
    ///             */
    ///                controller.AddInputPlugIn(input);
    ///            }
    ///            List<Type> outputTypes = NIPOController.SearchPlugIns(info.FullName, NIPO.Plugins.PlugInType.Output);
    ///            foreach (Type type in outputTypes)
    ///            {
    ///                Console.WriteLine(type.FullName);
    ///             /*
    ///             ... Configure Plugins
    ///             */
    ///                OutputPlugin outputPlugin = (OutputPlugin)type.Assembly.CreateInstance(type.FullName);
    ///               controller.AddOutputPlugin(outputPlugin);
    ///            }
    ///            
    ///            List<Type> processingTypes = NIPOController.SearchPlugIns(info.FullName, NIPO.Plugins.PlugInType.Processing);
    ///            foreach (Type type in processingTypes)
    ///            {
    ///                Console.WriteLine(type.FullName);
    ///             /*
    ///             ... Configure Plugins
    ///             */
    ///                chain.AddPlugin((ProcessingPlugin)type.Assembly.CreateInstance(type.FullName));
    ///            }
    ///             
    ///        }
    ///        controller.AddProcessingPlugin(chain);
    ///        controller.Start();
    ///]]>
    /// </code>
    /// </example>
    public sealed class NIPOController
    {
        #region Private memebers
        private readonly NIPOInstance _instance = new NIPOInstance();
        private readonly Logger _log = new Logger(typeof(NIPOController).FullName);
        private int _nextId = 0;
        #endregion

        #region Singleton

        private static NIPOController _singletonInstance = new NIPOController();

        /// <summary>
        /// Gets the NIPOController Singleton instance.
        /// </summary>
        /// <value>The instance.</value>
        public static NIPOController Instance
        {
            get { return _singletonInstance; }
            private set { _singletonInstance = value; }
        }
        #endregion

        private NIPOController()
        {
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="NIPOController"/> is reclaimed by garbage collection.
        /// </summary>
        ~NIPOController()
        {
            _instance.Clear();
        }


        #region Plugin Loading

        /// <summary>
        /// Searches for suitable assemblies (DLL or exe) that can be loaded by .NET
        /// </summary>
        /// <param eventName="directory">The directory to search in</param>
        /// <param eventName="searchSubdirectories">Indicates if the subdirectories of <paramref eventName="directory"/> should be included in the search</param>
        /// <returns>A list of Files that are suitable assemblies</returns>
        public static List<FileInfo> SearchAssemblies(DirectoryInfo directory, bool searchSubdirectories)
        {
            List<FileInfo> assemblies = new List<FileInfo>();
            SearchOption option = SearchOption.AllDirectories;
            if (!searchSubdirectories)
                option = SearchOption.TopDirectoryOnly;
            foreach (FileInfo file in directory.GetFiles("*.dll", option))
            {
                if (SearchPlugIns(file.FullName, PlugInType.Observer) != null)
                {
                    assemblies.Add(file);
                }
            }
            return assemblies;
        }

        /// <summary>
        /// Searches for PlugIns of a given PlugIn-Type
        /// </summary>
        /// <param eventName="assemblyPath">The assembly to crawl</param>
        /// <param eventName="type">The PlugIn-type to search for (e.g. InputPlugIn)</param>
        /// <returns>A list of types found in the assembly</returns>
        public static List<Type> SearchPlugIns(string assemblyPath, PlugInType type)
        {
            try
            {
                LocalAssemblyResolver.AddSearchPath(Path.GetDirectoryName(assemblyPath));
                Type searchType = Plugins.Plugin.GetType(type);

                Assembly a = Assembly.LoadFrom(assemblyPath);
                List<Type> types = new List<Type>();
                AssemblyName[] referencedAssemblies = a.GetReferencedAssemblies();
                foreach (AssemblyName name in referencedAssemblies)
                {
                    Assembly.Load(name);
                }
                foreach (Type t in a.GetExportedTypes())
                {
                    if (t.IsSubclassOf(searchType) && !t.IsAbstract)
                    {
                        types.Add(t);
                    }
                }
                return types;
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception during Assembly loading {0}", ex.Message);
                return null;
            }
        }

        /// <summary>
        /// Loads a certain PlugIn from the assembly specified with <paramref eventName="assemblyPath"/>.
        /// </summary>
        /// <param eventName="assemblyPath">Path to the assembly where the Type to load is contained</param>
        /// <param eventName="fullName">Full eventName of the type to instantiate</param>
        /// <param eventName="type">PlugIn Type</param>
        /// <returns>An _instance of the type specified with <paramref eventName="fullName"/></returns>
        public static Plugins.Plugin LoadPlugIn(string assemblyPath, string fullName, PlugInType type)
        {
            try
            {
                LocalAssemblyResolver.AddSearchPath(Path.GetDirectoryName(assemblyPath));
                Assembly a = Assembly.LoadFile(assemblyPath);
                Type searchType = Plugins.Plugin.GetType(type);
                AssemblyName[] referencedAssemblies = a.GetReferencedAssemblies();
                foreach (AssemblyName name in referencedAssemblies)
                {
                    Assembly.Load(name);
                }
                foreach (Type t in a.GetExportedTypes())
                {
                    if (t.BaseType == null) continue;
                    if (!t.FullName.Equals(fullName)) continue;
                    if (!t.BaseType.IsSubclassOf(searchType)) continue;
                    Plugins.Plugin i = (Plugins.Plugin)a.CreateInstance(fullName, true);
                    return i;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to load PlugIn: {0}\n{1}", ex.Message, ex.StackTrace);
            }

            return null;

        }
        #endregion

        #region Plugin Management
        /// <summary>
        /// Adds a new plugin to the corresponding list of plugins (determined by <paramref name="type"/>
        /// </summary>
        /// <param name="plugin">The plugin to add</param>
        /// <param name="type">Type of Plugin to add</param>
        /// <exception cref="ArgumentException">Supplied object is not of given <paramref name="type"/> or null</exception>
        public void AddPlugin(Plugin plugin, PlugInType type)
        {
            if (plugin == null)
                throw new ArgumentException("Plugin must not be null");
            if (plugin.ConfigurationObject == null)
                plugin.ConfigurationObject = new PluginConfiguration();
            plugin.ID = _nextId++;
            switch (type)
            {
                case PlugInType.Input:
                    {
                        if (plugin is InputPlugin)
                        {
                            _instance.AddInputPlugin(plugin as InputPlugin);
                        }
                        else
                        {
                            throw new ArgumentException("Supplied object is not of type InputPlugin");
                        }
                        break;
                    }
                case PlugInType.Output:
                    {
                        if (plugin is OutputPlugin)
                        {
                            _instance.AddOutputPlugin(plugin as OutputPlugin);
                        }
                        else
                        {
                            throw new ArgumentException("Supplied object is not of type OutputPlugin");
                        }
                        break;
                    }
                case PlugInType.Processing:
                    {
                        if (plugin is ProcessingPlugin)
                        {
                            _instance.ProcessingPlugin = plugin as ProcessingPlugin;
                        }
                        else
                        {
                            throw new ArgumentException("Supplied object is not of type ProcessinPlugin");
                        }
                        break;
                    }
                case PlugInType.Service:
                    {
                        if (plugin is ServicePlugin)
                        {
                            _instance.AddServicePlugin(plugin as ServicePlugin);
                        }
                        else
                        {
                            throw new ArgumentException("Supplied object is not of type ServicePlugin");
                        }
                        break;
                    }
                case PlugInType.Observer:
                    {
                        if (plugin is ObserverPlugin)
                        {
                            _instance.AddObserverPlugin(plugin as ObserverPlugin);
                        }
                        else
                        {
                            throw new ArgumentException("Supplied object is not of type ObserverPlugin");
                        }
                        break;
                    }
            }

        }

        /// <summary>
        /// Adds a service plugin.
        /// </summary>
        /// <param name="servicePlugin">The service plugin.</param>
        public void AddService(ServicePlugin servicePlugin)
        {
            AddPlugin(servicePlugin, PlugInType.Service);
        }



        /// <summary>
        /// Adds the input plug in to the list of selecte input plugins.
        /// </summary>
        /// <param name="inputPlugIn">The input plug in.</param>
        public void AddInputPlugIn(InputPlugin inputPlugIn)
        {
            AddPlugin(inputPlugIn, PlugInType.Input);
        }

        /// <summary>
        /// Adds the processing plugin.
        /// </summary>
        /// <param name="processingPlugin">The processing plugin.</param>
        public void AddProcessingPlugin(ProcessingPlugin processingPlugin)
        {
            AddPlugin(processingPlugin, PlugInType.Processing);
        }

        /// <summary>
        /// Adds an output plugin to the list of selected output plugins.
        /// </summary>
        /// <param name="outputPlugin">The output plugin.</param>
        public void AddOutputPlugin(OutputPlugin outputPlugin)
        {
            AddPlugin(outputPlugin, PlugInType.Output);
        }

        /// <summary>
        /// Adds an observer plugin.
        /// </summary>
        /// <param name="observerPlugin">The observer plugin.</param>
        public void AddObserver(ObserverPlugin observerPlugin)
        {
            AddPlugin(observerPlugin, PlugInType.Observer);
        }

        /// <summary>
        /// Returns a collection containing all plugins currently assigned to this instance.
        /// </summary>
        public ICollection<Plugin> CurrentlySelectedPlugins
        {
            get { return _instance.Plugins; }
        }
        #endregion

        #region Running NIPO

        /// <summary>
        /// Checks the plugin compatibility. This is an incremental process where every plugin in the plugin chain is verified to be able to interpred the data coming from its preceeding plugin.<br />
        /// This process is also used to determine the concrete data type and content exchanged between plugins.
        /// </summary>
        /// <returns></returns>
        private bool CheckPluginCompatibility()
        {
            //If there is a processing plugin
            if (_instance.ProcessingPlugin != null && _instance.ProcessingPlugin is IInputChecker)
            {
                foreach (InputPlugin inplugin in (IEnumerable)_instance.InputPlugins)
                {
                    IOutputChecker ip = inplugin as IOutputChecker;
                    if (ip == null) continue;
                    //Get supported Output formats from Input
                    ICollection<DataTransferObject> outFormats = ip.GetSupportedOutput();
                    //Ask processing for supported inputs
                    ICollection<DataTransferObject> buffersIn = (_instance.ProcessingPlugin as IInputChecker).GetAcceptableInput(outFormats);
                    //check result
                    if (buffersIn == null || buffersIn.Count == 0)
                    {
                        _log.WriteInfoFormat("Input {0} not compatible with Processing", inplugin.Name);
                        return false;
                    }
                    //Requested input must be contained in offered output
                    _log.WriteInfoFormat("Input {0} compatible with Processing", inplugin.Name);
                    if (!outFormats.Contains(buffersIn.GetEnumerator().Current))
                    {
                        _log.WriteInfoFormat("Input {0} not compatible with Processing", inplugin.Name);
                        return false;
                    }
                    //Set outputformat
                    if (!ip.DeliverOutput(buffersIn.GetEnumerator().Current))
                    {
                        _log.WriteInfoFormat("Input {0} not compatible with Processing", inplugin.Name);
                        return false;
                    }
                    //Set Input format
                    if (!(_instance.ProcessingPlugin as IInputChecker).ExpectInput(buffersIn.GetEnumerator().Current, inplugin.ID))
                    {
                        _log.WriteInfoFormat("Input {0} not compatible with Processing", inplugin.Name);
                        return false;
                    }
                    _log.WriteInfoFormat("Input {0} compatible with Processing", inplugin.Name);
                }
                if (!(_instance.ProcessingPlugin is IOutputChecker)) return true;
                //Check output plugin compatibility
                ICollection<DataTransferObject> processingOut = (_instance.ProcessingPlugin as IOutputChecker).GetSupportedOutput();
                foreach (OutputPlugin op in _instance.OutputPlugins)
                {
                    IInputChecker outplugin = op as IInputChecker;
                    if (outplugin == null) continue;

                    ICollection<DataTransferObject> buffersIn = outplugin.GetAcceptableInput(processingOut);
                    if (buffersIn == null || buffersIn.Count < 1)
                    {
                        _log.WriteInfoFormat("Output {0} not compatible with Processing", op.Name);
                        return false;
                    }
                    (_instance.ProcessingPlugin as IOutputChecker).DeliverOutput(buffersIn.GetEnumerator().Current);
                    outplugin.ExpectInput(buffersIn.GetEnumerator().Current, _instance.ProcessingPlugin.ID);
                    _log.WriteInfoFormat("Output {0} compatible with Processing", op.Name);
                }
            }
            else //If there is no processing plugin
            {
                foreach (InputPlugin ip in _instance.InputPlugins)
                {
                    IOutputChecker inplugin = ip as IOutputChecker;
                    if (inplugin == null) continue;
                    //Every output plugin must be prepared for every input plugin
                    ICollection<DataTransferObject> buffersOut = inplugin.GetSupportedOutput();
                    foreach (OutputPlugin op in _instance.OutputPlugins)
                    {
                        IInputChecker outplugin = op as IInputChecker;
                        if (outplugin == null) continue;

                        ICollection<DataTransferObject> buffersIn = outplugin.GetAcceptableInput(buffersOut);
                        if (buffersIn == null || buffersIn.Count < 1)
                        {
                            _log.WriteInfoFormat("Output {0} not compatible with Input", op.Name);
                            return false;
                        }
                        _log.WriteInfoFormat("Output {0} compatible with Input", ip.Name);
                        if (!inplugin.DeliverOutput(buffersIn.GetEnumerator().Current)) return false;
                        if (!outplugin.ExpectInput(buffersIn.GetEnumerator().Current, ip.ID)) return false;
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// Starts the NIPO instance.<br/>
        /// 	<list type="number">
        /// 		<item>All preconditions for the processing chain are checked (number of plugins in I,P,O)</item>
        /// 		<item>For all plugins <see cref="Plugin.Initialize"/> is called for every plugin (Here a plugin is given the chance to register for user-defined events)</item>
        /// 		<item>Compatibility check to ensure that every plugin can interpred the data coming from its preceeding plugin (if it implements IInputChecker or IOutputChecker)</item>
        /// 		<item>All plugins must return <c>true</c> for <see cref="Plugin.IsConfigured"/>, else the execution is cancelled</item>
        /// 		<item>Every Input Plugin is started in its dedicated thread, this causes the whole instance to start</item>
        /// 	</list>
        /// </summary>
        /// <exception cref="InvalidOperationException">There must be at least one Input and one Output Plugin for a NIPO instance to start</exception>
        /// <exception cref="Exception">All plugins must be configured properly</exception>
        /// <exception cref="NotSupportedException">If Plugins are not compatible. Check log for more information.</exception>
        public void Start()
        {
            _log.WriteInfo("Starting NIPO instance");
            //1
            if (_instance.InputPlugins.Count == 0)
                throw new InvalidOperationException(
                    "There must be at least one Input Plugin for a NIPO instance to start");
            if (_instance.OutputPlugins.Count == 0)
                throw new InvalidOperationException(
                    "There must be at least one Output Plugin for a NIPO instance to start");
            //2
            foreach (Plugin plugin in _instance.Plugins)
                plugin.Initialize();
            //3
            if (!CheckPluginCompatibility())
                throw new NotSupportedException("Plugins are not compatible. Check log for more information.");
            _log.WriteDebug("Instance info:");
            foreach (Plugin plugin in _instance.Plugins)
            {
                _log.WriteDebugFormat("{0}-{1}", plugin.Name, plugin.Version);
                if (!plugin.IsConfigured)
                    throw new Exception("Plugin " + plugin.Name + " is not properly configured");
            }

            foreach (InputPlugin input in _instance.InputPlugins)
            {
                if (_instance.ProcessingPlugin != null)
                    input.NewData += _instance.ProcessingPlugin.ProcessData;
                else
                {
                    foreach (OutputPlugin plugin in _instance.OutputPlugins)
                    {
                        input.NewData += plugin.OutputData;
                    }
                }
            }
            if (_instance.ProcessingPlugin != null)
            {
                foreach (OutputPlugin plugin in _instance.OutputPlugins)
                {
                    _instance.ProcessingPlugin.NewData += plugin.OutputData;
                }
            }
            //Starting input
            foreach (InputPlugin input in _instance.InputPlugins)
            {
                input.Start();
            }
            _log.WriteInfo("NIPO instance started");
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public void Stop()
        {
            _log.WriteInfo("Stopping NIPO instance");
            foreach (InputPlugin input in _instance.InputPlugins)
                input.Stop();
            _log.WriteInfo("NIPO instance stopped");
        }

        /// <summary>
        /// Resets this instance. This keeps all selected plugins but resets the IPO chain (by calling <see cref="IPOPlugin.Reset" />).
        /// </summary>
        public void Reset()
        {
            foreach (InputPlugin input in _instance.InputPlugins)
            {
                input.Reset();
                input.InternalReset();
            }
            foreach (OutputPlugin output in _instance.OutputPlugins)
            {
                output.Reset();
                output.InternalReset();
            }
            if (_instance.ProcessingPlugin != null)
            {
                _instance.ProcessingPlugin.Reset();
                _instance.ProcessingPlugin.InternalReset();
            }
        }

        /// <summary>
        /// Clears this instance. This delets all plugins (IPO, Service and Observer) of the NIPO instance
        /// </summary>
        public void Clear()
        {
            _instance.Clear();
        }

        #endregion

        #region Storing and loading instances
        /// <summary>
        /// Stores the instance. Serializes recreation info to a single file.
        /// </summary>
        /// <param name="targetFile">The target file.</param>
        /// <returns></returns>
        public bool Store(FileInfo targetFile)
        {
            /*
             * We Store:
             * 1. Number of assembly resolver paths
             * 2. All assembly resolver paths (to later load assemblies by name) as DirectoryInfo
             * 3. Number of plugins
             * 4. All plugin types and their configuration
             */
            try
            {
                using (FileStream fs = File.Open(targetFile.FullName, FileMode.Create))
                {
                    ICollection<Plugin> plugins = _instance.Plugins;
                    BinaryFormatter formatter = new BinaryFormatter();
                    //1
                    formatter.Serialize(fs, LocalAssemblyResolver.GetSearchPaths().Count);
                    //2
                    foreach (string path in LocalAssemblyResolver.GetSearchPaths())
                    {

                        DirectoryInfo info;
                        if (string.IsNullOrEmpty(path))
                            info = new DirectoryInfo(Directory.GetCurrentDirectory());
                        else
                            info = new DirectoryInfo(path);
                        formatter.Serialize(fs, info);
                    }
                    //3
                    formatter.Serialize(fs, plugins.Count);
                    //4
                    foreach (Plugin plugin in plugins)
                    {
                        formatter.Serialize(fs, plugin.GetType());
                        if (plugin.ConfigurationObject == null)
                            plugin.ConfigurationObject = new PluginConfiguration();
                        formatter.Serialize(fs, plugin.ConfigurationObject);
                    }
                    return true;
                }
            }
            catch (IOException)
            {
                return false;
            }
        }
        /// <summary>
        /// Stores the current NIPO instance to a SOAP formatted XML file
        /// </summary>
        /// <param name="targetFile">The file to store to</param>
        /// <returns><c>true</c> if successful, <c>false</c> otherwise</returns>
        public bool StoreToXML(FileInfo targetFile)
        {
            /*
             * We Store:
             * 1. All assembly resolver paths (to later load assemblies by name) as DirectoryInfo
             * 3. Number of plugins
             * 4. All plugin types and their configuration
             */
            try
            {
                using (FileStream fs = File.Open(targetFile.FullName, FileMode.Create))
                {
                    ICollection<Plugin> plugins = _instance.Plugins;
                    NIPOSerializationObject serializationObject = new NIPOSerializationObject();
                    serializationObject.AssemblySearchPaths = new string[LocalAssemblyResolver.GetSearchPaths().Count];
                    //1
                    int index = 0;
                    foreach (string path in LocalAssemblyResolver.GetSearchPaths())
                    {

                        DirectoryInfo info;
                        if (string.IsNullOrEmpty(path))
                            info = new DirectoryInfo(Directory.GetCurrentDirectory());
                        else
                            info = new DirectoryInfo(path);
                        serializationObject.AssemblySearchPaths[index] = info.FullName;
                        index++;
                    }
                    serializationObject.Plugins = new Type[plugins.Count];
                    serializationObject.ConfigurationObjects = new PluginConfiguration[plugins.Count];
                    //4
                    List<Type> types = new List<Type>();
                    index = 0;
                    foreach (Plugin plugin in plugins)
                    {
                        serializationObject.Plugins[index] = plugin.GetType();
                        types.Add(plugin.GetType());
                        PluginConfiguration config = plugin.ConfigurationObject ?? new PluginConfiguration();
                        if (!types.Contains(config.GetType()))
                        {
                            types.Add(config.GetType());
                        }
                        serializationObject.ConfigurationObjects[index] = config;
                        index++;
                    }
                    SoapFormatter formatter = new SoapFormatter();
                    formatter.Serialize(fs, serializationObject);
                    return true;
                }
            }
            catch (IOException)
            {
                return false;
            }
        }

        /// <summary>
        /// Loads the specified info.
        /// </summary>
        /// <param name="info">The info.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException">File does not exist</exception>
        private static NIPOController Load(FileInfo info)
        {
            /*
             * 1. Load number of assembly resolver paths
             * 2. Load paths and pass to LocalAssemblyResolver
             * 3. Load number of plugins
             * 4. Load plugin types, instantiate, load their configuration and add to NIPO controller
             */
            if (!info.Exists)
                throw new ArgumentException("File does not exist");
            using (FileStream fs = info.OpenRead())
            {
                BinaryFormatter formatter = new BinaryFormatter();
                int numPaths = (int)formatter.Deserialize(fs);
                for (int i = 0; i < numPaths; i++)
                {
                    DirectoryInfo searchDirectory = (DirectoryInfo)formatter.Deserialize(fs);
                    LocalAssemblyResolver.AddSearchPath(searchDirectory.FullName);
                }
                int numPlugins = (int)formatter.Deserialize(fs);
                NIPOController controller = new NIPOController();
                for (int i = 0; i < numPlugins; i++)
                {
                    Type pluginType = (Type)formatter.Deserialize(fs);
                    Plugin plugin = (Plugin)pluginType.Assembly.CreateInstance(pluginType.FullName);
                    PluginConfiguration config = (PluginConfiguration)formatter.Deserialize(fs);
                    plugin.ConfigurationObject = config;
                    controller.AddPlugin(plugin, Plugin.GetType(pluginType));
                }
                return controller;
            }
        }
        /// <summary>
        /// Restores a NIPO instance from an SOAP formatted XML file
        /// </summary>
        /// <param name="info">File info where to load from</param>
        /// <returns>A configured NIPOController</returns>
        private static NIPOController LoadFromXML(FileInfo info)
        {
            /*
             * 1. Load number of assembly resolver paths
             * 2. Load paths and pass to LocalAssemblyResolver
             * 3. Load number of plugins
             * 4. Load plugin types, instantiate, load their configuration and add to NIPO controller
             */
            if (!info.Exists)
                throw new ArgumentException("File does not exist");
            using (FileStream fs = info.OpenRead())
            {
                NIPOController controller = new NIPOController();
                SoapFormatter formatter = new SoapFormatter();
                NIPOSerializationObject serObj = formatter.Deserialize(fs) as NIPOSerializationObject;
                if (serObj == null)
                    throw new ArgumentException("Invalid File to load");
                foreach (string path in serObj.AssemblySearchPaths)
                {
                    LocalAssemblyResolver.AddSearchPath(path);
                }
                for (int i = 0; i < serObj.Plugins.Length; i++)
                {
                    Type t = serObj.Plugins[i];
                    PluginConfiguration conf = serObj.ConfigurationObjects[i];
                    Plugin plugin = (Plugin)t.Assembly.CreateInstance(t.FullName);
                    plugin.ConfigurationObject = conf;
                    controller.AddPlugin(plugin, Plugin.GetType(t));
                }
                return controller;
            }
        }

        /// <summary>
        /// Loads a NIPO instance from the given filepath and assigns it to the singleton instance.
        /// </summary>
        /// <param name="filepath"></param>
        public void Load(string filepath)
        {
            Instance = Load(new FileInfo(filepath));
        }
        /// <summary>
        /// Loads a NIPO instance from a SOAP formatted XML file
        /// </summary>
        /// <param name="filepath">The path to load from</param>
        public void LoadFromXML(string filepath)
        {
            Instance = LoadFromXML(new FileInfo(filepath));
        }

        #endregion
    }
}